import { Model, IFindOptions, ICountOptions } from "sequelize-typescript";
import CustomerError from "../error";

export interface IStorage<T> { 
    get(key: string): Promise<T>;
    set(obj: T): Promise<any>;
    remove(obj: T): Promise<any>;
    removeAll(): Promise<any>;
}

export interface IModel<T> { 
    findById: Function;
}

export class BaseService<T extends Model<T>> { 
    constructor(public $storage: IStorage<T>, public model: any) {
    }
    async get(id: string): Promise<T>{ 
        let obj = await this.$storage.get(id);
        if (obj)
            return obj;
        obj = await this.model.findById(id);
        if (obj)
            this.$storage.set(obj);
        return obj;
    }
    async destory(obj: T): Promise<boolean> { 
        let b = await this.model.destroy(obj);
        await this.$storage.remove(obj);
        return !!b;
    }
    async save(obj: T): Promise<T> { 
        if (!(obj instanceof Model)) { 
            obj = await this.model.build(obj);
        }
        obj = await obj.save();
        return obj;
    }
    async add(obj: T): Promise<T> { 
        let oldObj = await this.model.findById(obj.id);
        obj = await oldObj.update(obj);
        await this.$storage.set(obj);
        return obj;
    }
    async count(options: ICountOptions<T>): Promise<number> { 
        return this.model.count(options);
    }
    async find(options: IFindOptions<T>, page: number = 0, pagesize: number = 20): Promise<Pager<T>>{ 
        const pager = new Pager<T>(this.model, this, options, page, pagesize);
        await pager.fetch();
        return pager;
    }
    async findOne(options: IFindOptions<T>): Promise<T> { 
        let obj = await this.model.findOne(options);
        await this.$storage.set(obj);
        return obj;
    }
    async all(options: IFindOptions<T>): Promise<T[]> { 
        if (!options.attributes) {
            options.attributes = ['id'];
        }
        const page = 0;
        const pager = await this.find(options, page);
        const allowMax = 1000;
        if (pager.count > allowMax) {
            throw new CustomerError(400, `当前记录数已超过允许最大不分页数目${allowMax},请分页查询`);
        }
        let result: T[] = [];
        while (true) { 
            result = result.concat(pager.items);
            if (!pager.hasNextPage()) { 
                break;
            }
            await pager.nextPage();
        }
        return result;
    }
}

export class Pager<T extends Model<T>> { 
    public items: T[] = [];
    public count: number = 0;
    constructor(public model: any, public service: BaseService<T>, public options: IFindOptions<T>, public page: number=0, public pagesize: number=20) { 
    }
    async fetch() :Promise<ThisType<T>>{ 
        this.options.limit = this.pagesize;
        this.options.offset = this.page * this.pagesize;
        if (!this.options.attributes) { 
            this.options.attributes = ['id'];
        }
        if (!this.options.order) { 
            this.options.order = [['id', 'desc']]
        }
        let { rows, count } = await this.model.findAndCountAll(this.options);
        let ps: Promise<T>[] = rows.map(async (row: {id: any}) => {
            return this.service.get(row.id);
        });
        rows = await Promise.all<T>(ps);
        this.items = rows;
        this.count = count;
        return this;
    }
    hasNextPage(): boolean { 
        return this.page < this.totalPage();
    }
    async nextPage() :Promise<ThisType<T>>{ 
        if (!this.hasNextPage()) {
            throw new CustomerError(400, '没有下一页了');
        }
        this.page = this.page + 1;
        await this.fetch();
        return this;
    }
    hasPrevPage(): boolean { 
        return this.page > 0;
    }
    async prevPage() :Promise<ThisType<T>>{ 
        if (!this.hasPrevPage()) { 
            throw new CustomerError(400, '没有上一页了');
        }
        this.page = this.page - 1;
        await this.fetch();
        return this;
    }
    totalPage(): number { 
        return Math.ceil(this.count / this.pagesize);
    }
    toJSON() { 
        return {
            page: this.page,
            pagesize: this.pagesize,
            count: this.count,
            totalPage: this.totalPage(),
            items: this.items,
        }
    }
}